<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Web Component 综合示例: 可折叠面板</title>
    <style>
        /* 外部样式：用于页面整体布局和自定义元素本身的样式 */
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            line-height: 1.6;
            margin: 20px;
            background-color: #f4f4f4;
            color: #333;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 20px;
        }

        h1 {
            color: #2c3e50;
            margin-bottom: 30px;
        }

        /* 外部样式，作用于自定义元素本身 */
        collapsible-panel {
            width: 80%;
            max-width: 600px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            border-radius: 8px;
            overflow: hidden; /* 确保圆角和阴影生效 */
            background-color: #fff;
        }

        /* 外部按钮样式 */
        .controls button {
            padding: 10px 20px;
            margin: 0 10px;
            background-color: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 1em;
            transition: background-color 0.3s ease;
        }

        .controls button:hover {
            background-color: #0056b3;
        }

        .log-area {
            width: 80%;
            max-width: 600px;
            background-color: #e9ecef;
            padding: 15px;
            border-radius: 5px;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
            color: #495057;
            border: 1px solid #ced4da;
            max-height: 150px;
            overflow-y: auto;
            white-space: pre-wrap; /* 允许文本换行 */
        }
    </style>
</head>
<body>
    <h1>综合 Web Component 示例：可折叠面板</h1>

    <div class="controls">
        <button onclick="toggleFirstPanel()">切换第一个面板</button>
        <button onclick="removeFirstPanel()">移除第一个面板</button>
        <button onclick="addPanel()">添加新面板</button>
    </div>

    <h2>示例面板：</h2>
    <!-- 带有默认展开属性的面板 -->
    <collapsible-panel open>
        <h3 slot="panel-title">关于 Web Components</h3>
        <p>Web Components 是一套 Web 平台 API，允许你创建可重用、封装的自定义 HTML 标签。</p>
        <p>它由 Custom Elements、Shadow DOM 和 HTML Templates 组成。</p>
        <p>这个段落是面板的主要内容。</p>
    </collapsible-panel>

    <!-- 默认折叠的面板 -->
    <collapsible-panel>
        <h3 slot="panel-title">前端技术栈</h3>
        <p>除了 Web Components，现代前端还有许多框架和工具：</p>
        <ul>
            <li>React</li>
            <li>Vue.js</li>
            <li>Angular</li>
            <li>Webpack/Vite</li>
            <li>TypeScript</li>
        </ul>
        <p>学习的道路漫长而有趣！</p>
    </collapsible-panel>

    <!-- 另一个面板，带有更复杂的内容 -->
    <collapsible-panel>
        <div slot="panel-title">
            <span style="color: blue; font-weight: bold;">复杂标题</span>
            <small>(点击展开/折叠)</small>
        </div>
        <p>这里可以放置任何 HTML 内容，包括图片、视频、表单等等。</p>
        <img src="https://via.placeholder.com/150/007bff/FFFFFF?text=Placeholder" alt="示例图片" style="max-width: 100%; height: auto; display: block; margin-top: 10px;">
        <button onclick="alert('面板内部的按钮被点击了！')">面板内部按钮</button>
    </collapsible-panel>

    <h2>事件日志：</h2>
    <div id="log" class="log-area"></div>

    <script>
        // 获取日志区域
        const logArea = document.getElementById('log');
        let logCounter = 0;

        function appendLog(message) {
            logCounter++;
            const timestamp = new Date().toLocaleTimeString();
            logArea.textContent += `[${timestamp}] ${message}\n`;
            logArea.scrollTop = logArea.scrollHeight; // 滚动到底部
        }

        // 1. 定义组件的 HTML 模板
        const panelTemplate = document.createElement('template');
        panelTemplate.innerHTML = `
            <style>
                /* Shadow DOM 内部样式：完全封装，不影响外部，外部不影响我 */
                .panel-header {
                    background-color: #3498db;
                    color: white;
                    padding: 15px 20px;
                    cursor: pointer;
                    display: flex;
                    justify-content: space-between;
                    align-items: center;
                    border-bottom: 1px solid rgba(255, 255, 255, 0.2);
                    user-select: none; /* 防止文本被选中 */
                }

                .panel-header:hover {
                    background-color: #2980b9;
                }

                .toggle-icon {
                    width: 0;
                    height: 0;
                    border-left: 6px solid transparent;
                    border-right: 6px solid transparent;
                    border-top: 6px solid white;
                    transition: transform 0.2s ease-in-out;
                }

                .panel-content {
                    padding: 20px;
                    border-top: none;
                    overflow: hidden;
                    transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out;
                    max-height: 0; /* 初始折叠状态 */
                    padding-top: 0;
                    padding-bottom: 0;
                    opacity: 0; /* 内容隐藏 */
                }

                /* 当面板打开时，应用这些样式 */
                :host([open]) .panel-content {
                    max-height: 500px; /* 足够大以显示内容，但要大于实际内容高度 */
                    padding-top: 20px;
                    padding-bottom: 20px;
                    opacity: 1; /* 内容显示 */
                }

                :host([open]) .toggle-icon {
                    transform: rotate(180deg); /* 箭头向上 */
                }

                /* 投射内容的样式：通过 ::slotted 伪元素 */
                ::slotted(h3), ::slotted(div) {
                    margin: 0; /* 移除默认外边距 */
                    line-height: 1.2;
                }

                ::slotted(p), ::slotted(ul), ::slotted(img), ::slotted(button) {
                    margin-bottom: 10px;
                }
            </style>
            <div class="panel-header">
                <!-- 具名槽位，用于标题 -->
                <slot name="panel-title">默认标题</slot>
                <div class="toggle-icon"></div>
            </div>
            <div class="panel-content">
                <!-- 默认槽位，用于内容 -->
                <slot></slot>
            </div>
        `;

        // 2. 定义 Custom Element 类
        class CollapsiblePanel extends HTMLElement {
            // 声明要观察的属性
            static get observedAttributes() {
                return ['open'];
            }

            constructor() {
                super(); // 必须调用 super()

                // 创建 Shadow DOM 并附加到组件
                this.attachShadow({ mode: 'open' }); // 'open' 模式允许外部 JS 访问 shadowRoot

                // 将模板内容克隆并添加到 Shadow DOM 中
                this.shadowRoot.appendChild(panelTemplate.content.cloneNode(true));

                // 缓存内部 DOM 元素引用
                this.$header = this.shadowRoot.querySelector('.panel-header');
                this.$content = this.shadowRoot.querySelector('.panel-content');

                // 绑定事件处理器的 'this' 上下文
                this._togglePanel = this._togglePanel.bind(this);

                appendLog('CollapsiblePanel: 构造函数被调用！');
            }

            // 生命周期回调：当元素被添加到文档时调用
            connectedCallback() {
                appendLog('CollapsiblePanel: connectedCallback - 元素被添加到DOM。');

                // 根据初始的 'open' 属性设置面板状态
                // this.hasAttribute('open') 检查属性是否存在，而不是其值
                this._updatePanelState(this.hasAttribute('open'));

                // 添加事件监听器
                this.$header.addEventListener('click', this._togglePanel);

                // 监听自定义事件，演示事件冒泡和穿透 Shadow DOM
                this.addEventListener('panel-toggled', (event) => {
                    appendLog(`CollapsiblePanel: 内部监听到 'panel-toggled' 事件。新状态: ${event.detail.isOpen}`);
                });
            }

            // 生命周期回调：当元素从文档中移除时调用，进行清理工作
            disconnectedCallback() {
                appendLog('CollapsiblePanel: disconnectedCallback - 元素从DOM中移除，进行清理。');
                // 移除事件监听器，防止内存泄漏
                this.$header.removeEventListener('click', this._togglePanel);
            }

            // 生命周期回调：当观察的属性发生变化时调用
            attributeChangedCallback(name, oldValue, newValue) {
                appendLog(`CollapsiblePanel: attributeChangedCallback - 属性 "${name}" 从 "${oldValue}" 变为 "${newValue}"。`);
                if (name === 'open') {
                    // 当 'open' 属性存在（或值为非空字符串）时，视为 true
                    const isOpen = newValue !== null;
                    this._updatePanelState(isOpen);
                }
            }

            // 内部方法：更新面板的展开/折叠状态
            _updatePanelState(isOpen) {
                if (isOpen) {
                    this.setAttribute('open', ''); // 添加 open 属性 (值可以为空)
                } else {
                    this.removeAttribute('open'); // 移除 open 属性
                }
                // 这里实际上是设置了属性，attributeChangedCallback 会负责后续更新
                // 但为了即时反馈，我们可以在此直接操作 DOM
                // 或者让 attributeChangedCallback 成为唯一的更新来源
            }

            // 内部方法：处理点击切换面板
            _togglePanel() {
                const isOpen = this.hasAttribute('open');
                this._updatePanelState(!isOpen); // 切换状态

                // 派发自定义事件，通知外部面板状态已改变
                // bubbles: true 允许事件冒泡到父元素
                // composed: true 允许事件穿透 Shadow DOM 边界
                this.dispatchEvent(new CustomEvent('panel-toggled', {
                    bubbles: true,
                    composed: true,
                    detail: { isOpen: !isOpen } // 传递新状态
                }));

                appendLog(`CollapsiblePanel: 触发点击，面板状态切换为 ${!isOpen ? '展开' : '折叠'}`);
            }

            // 公共方法：提供一个编程接口来切换面板
            toggle() {
                this._togglePanel();
            }

            // 公共方法：检查面板是否展开
            isOpen() {
                return this.hasAttribute('open');
            }
        }

        // 3. 注册自定义元素
        // 'collapsible-panel' 是你的新 HTML 标签名，必须包含连字符 '-'
        customElements.define('collapsible-panel', CollapsiblePanel);


        // =========================================================
        // 外部 JavaScript 交互演示
        // =========================================================

        // 监听所有 collapsible-panel 实例的 'panel-toggled' 事件
        document.addEventListener('panel-toggled', (event) => {
            const panel = event.target; // 获取触发事件的元素
            appendLog(`外部监听到 '${panel.tagName.toLowerCase()}' (ID: ${panel.id || '无ID'}) 状态改变: ${event.detail.isOpen ? '展开' : '折叠'}`);
        });

        function toggleFirstPanel() {
            const firstPanel = document.querySelector('collapsible-panel');
            if (firstPanel) {
                firstPanel.toggle(); // 调用组件的公共方法
            }
        }

        function removeFirstPanel() {
            const firstPanel = document.querySelector('collapsible-panel');
            if (firstPanel) {
                firstPanel.remove(); // 移除元素，会触发 disconnectedCallback
            }
        }

        let panelCounter = 0;
        function addPanel() {
            const newPanel = document.createElement('collapsible-panel');
            newPanel.id = `dynamic-panel-${panelCounter++}`; // 为动态添加的面板设置 ID
            newPanel.innerHTML = `
                <h3 slot="panel-title">动态添加的面板 ${panelCounter}</h3>
                <p>这是在运行时通过 JavaScript 添加的新面板。</p>
                <p>它的状态可以独立控制。</p>
            `;
            document.body.appendChild(newPanel);
            appendLog(`添加了一个新的面板：${newPanel.id}`);
        }
    </script>
</body>
</html>
